package com.the_last.aspect;

import com.alibaba.fastjson2.JSON;
import com.the_last.pojo.bo.LoginUser;
import com.the_last.utils.IpUtils;
import com.the_last.utils.Result;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;

@Component
@Aspect
@Slf4j
public class LogAspect {
    
    @Pointcut("execution(* com.the_last.controller..*(..))")
    public void allInterface() {
    }
    
    @Pointcut("execution(void com.the_last.controller..*(..))")
    public void allVoidInterface() {
    }
    
    @Around("allVoidInterface()")
    public void doVoidAround(ProceedingJoinPoint pjp) throws Throwable {
        executeWithLogging(pjp, true);
    }
    
    @Around("allInterface() && !allVoidInterface()")
    public Result doAround(ProceedingJoinPoint pjp) throws Throwable {
        return (Result) executeWithLogging(pjp, false);
    }
    
    private Object executeWithLogging(ProceedingJoinPoint pjp, boolean isVoidMethod) throws Throwable {
        long startTime = System.currentTimeMillis();
        RequestContext context = buildRequestContext(pjp, startTime);
        
        Throwable exception = null;
        Object result = null;
        boolean hasException = false;
        
        try {
            result = pjp.proceed();
            if (!isVoidMethod) {
                Result resultObj = (Result) result;
                context.setResponseCode(resultObj.getCode());
                context.setResponseData(JSON.toJSONString(resultObj.getData()));
            }
        } catch (Throwable e) {
            hasException = true;
            exception = e;
            context.setResponseCode(500);
        }
        
        context.setEndTime(System.currentTimeMillis());
        context.setHasException(hasException);
        
        logRequest(context);
        
        if (hasException) {
            logException(context, exception);
            throw exception;
        }
        
        return result;
    }
    
    private RequestContext buildRequestContext(ProceedingJoinPoint pjp, long startTime) {
        RequestContext context = new RequestContext();
        context.setTraceId(UUID.randomUUID().toString().replace("-", "").substring(0, 8));
        context.setStartTime(startTime);
        
        Signature signature = pjp.getSignature();
        context.setMethodName(signature.getDeclaringTypeName() + "." + signature.getName());
        context.setArgs(JSON.toJSONString(filterArgs(pjp.getArgs())));
        
        HttpServletRequest request = ((ServletRequestAttributes)
                Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        context.setRequestUri(request.getRequestURI());
        context.setRequestMethod(request.getMethod());
        context.setIpAddress(IpUtils.getIpAddr(request));
        context.setUserAgent(request.getHeader("User-Agent"));
        context.setSessionId(request.getSession().getId());
        context.setUsername(getCurrentUsername());
        
        return context;
    }
    
    private void logRequest(RequestContext context) {
        long executionTime = context.getEndTime() - context.getStartTime();
        
        if (context.getResponseData() != null) {
            log.info("""
                            
                            \r=======================================
                            \r请求追踪ID: {}
                            \r请求地址: {}
                            \r请求方式: {}
                            \r方法名称: {}
                            \r请求参数: {}
                            \rIP地址: {}
                            \r用户代理: {}
                            \r当前用户: {}
                            \r会话ID: {}
                            \r响应码: {}
                            \r响应数据: {}
                            \r执行时间: {}ms
                            \r请求状态: {}
                            \r系统内存使用: {}MB
                            \r=======================================
                            \r""",
                    context.getTraceId(),
                    context.getRequestUri(),
                    context.getRequestMethod(),
                    context.getMethodName(),
                    context.getArgs(),
                    context.getIpAddress(),
                    context.getUserAgent(),
                    context.getUsername(),
                    context.getSessionId(),
                    context.getResponseCode(),
                    truncateResponseData(context.getResponseData()),
                    executionTime,
                    context.isHasException() ? "异常" : "成功",
                    getMemoryUsage()
            );
        } else {
            log.info("""
                            
                            \r=======================================
                            \r请求追踪ID: {}
                            \r请求地址: {}
                            \r请求方式: {}
                            \r方法名称: {}
                            \r请求参数: {}
                            \rIP地址: {}
                            \r用户代理: {}
                            \r当前用户: {}
                            \r会话ID: {}
                            \r执行时间: {}ms
                            \r请求状态: {}
                            \r=======================================
                            \r""",
                    context.getTraceId(),
                    context.getRequestUri(),
                    context.getRequestMethod(),
                    context.getMethodName(),
                    context.getArgs(),
                    context.getIpAddress(),
                    context.getUserAgent(),
                    context.getUsername(),
                    context.getSessionId(),
                    executionTime,
                    context.isHasException() ? "异常" : "成功"
            );
        }
    }
    
    private void logException(RequestContext context, Throwable exception) {
        log.error("请求执行异常 [TraceID: {}] - 方法: {} - 异常类型: {} - 异常信息: {}",
                context.getTraceId(), context.getMethodName(),
                exception.getClass().getSimpleName(), exception.getMessage());
    }
    
    private String truncateResponseData(String responseData) {
        if (responseData == null) return "null";
        return responseData.length() > 500 ? responseData.substring(0, 500) + "..." : responseData;
    }
    
    
    /**
     * 过滤参数
     *
     * @param args 排除与 HttpServletRequest HttpServletResponse MultipartFilter LoginUser 有关的参数
     * @return 符合要求的参数集合
     */
    private List<Object> filterArgs(Object[] args) {
        return Arrays.stream(args).filter(object -> !(object instanceof MultipartFile)
                && !(object instanceof HttpServletRequest)
                && !(object instanceof HttpServletResponse)
                && !(object instanceof HttpSession)
                && !(object instanceof UserDetails)
        ).collect(Collectors.toList());
    }
    
    private String getCurrentUsername() {
        try {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication != null && authentication.isAuthenticated() &&
                    !authentication.getPrincipal().equals("anonymousUser")) {
                return authentication.getName();
            }
            return "匿名用户";
        } catch (Exception e) {
            return "未知用户";
        }
    }
    
    private long getMemoryUsage() {
        Runtime runtime = Runtime.getRuntime();
        return (runtime.totalMemory() - runtime.freeMemory()) / (1024 * 1024);
    }
    
    @Data
    private static class RequestContext {
        private String traceId;
        private long startTime;
        private long endTime;
        private String methodName;
        private String args;
        private String requestUri;
        private String requestMethod;
        private String ipAddress;
        private String userAgent;
        private String username;
        private String sessionId;
        private Integer responseCode;
        private String responseData;
        private boolean hasException;
    }
}
